classdef DookuSynthesizer < handle
    %%DOOKUZYNTHESIZER Dooku Synthesizer main window GUI class
    
    properties
        % Figure variables
        hfig;
        handles;
        
        % Track variables
        track;
        trackInd;
        trackFileName;
        trackFilePath;
        methodInd;
        
        % Song variables
        song;
        songName;
        songVolume;
        songReverbInd;
        songReverbSet; %Struct
        
        % Windows classes variables
        reverbWindow;
        karplusStrongWindow;
        effectWindow;
        
        % Play variables
        player;
        playStatus;
        playAxes;
        xPlay;
    end
    
    methods
        
        function obj = DookuSynthesizer
            % Figure Load
            obj.hfig = hgload('principalGUI.fig');
            obj.handles = guihandles(obj.hfig);
            
            % Background Image
            img = imread('Black3.jpg');
            image(img, 'Parent', obj.handles.background);
            
            % Objects Creation
            obj.reverbWindow = ReverbWindow;
            obj.karplusStrongWindow = KarplusStrongWindow;
            obj.effectWindow = EffectWindow;
            
            % Load variables
%             obj.loadVariables();
            
            % Callbacks Definitions
            set(obj.handles.mainMenu, 'CloseRequestFcn', @(src, event) closeProgram(obj, src, event));
            set(obj.handles.track_add, 'Callback', @(src, event) newTrackSelect(obj, src, event));
            set(obj.handles.track_input, 'Callback', @(src, event) inputFileSelect(obj, src, event));
            set(obj.handles.trackList, 'Callback', @(src, event) trackListSelect(obj, src, event));
            set(obj.handles.track_remove, 'Callback', @(src, event) trackRemove(obj, src, event));
            set(obj.handles.methodMenu, 'Callback', @(src, event) methodSelect(obj, src, event));
            set(obj.handles.track_methodset, 'Callback', @(src, event) methodSettings(obj, src, event));
            set(obj.handles.track_addeffect, 'Callback', @(src, event) addEffectSelect(obj, src, event));
            set(obj.handles.effectsList, 'Callback', @(src, event) effectListSelect(obj, src, event));
            set(obj.handles.track_effectset, 'Callback', @(src, event) effectSettings(obj, src, event));
            set(obj.handles.track_effectrem, 'Callback', @(src, event) effectRemove(obj, src, event));
            set(obj.handles.song_new, 'Callback', @(src, event) newSong(obj, src, event));
            set(obj.handles.song_save, 'Callback', @(src, event) saveSong(obj, src, event));
            set(obj.handles.song_load, 'Callback', @(src, event) loadSong(obj, src, event));
            set(obj.handles.reverbMenu, 'Callback', @(src, event) reverbSelect(obj, src, event));
            set(obj.handles.song_revset, 'Callback', @(src, event) reverbSettings(obj, src, event));
            bg_image = imread('play.png');
            set(obj.handles.track_play, 'Callback', @(src, event) playTrack(obj, src, event), 'CData', bg_image);
            set(obj.handles.song_play, 'Callback', @(src, event) playSong(obj, src, event), 'CData', bg_image);
            bg_image = imread('stop.png');
            set(obj.handles.track_stop, 'Callback', @(src, event) stopTrack(obj, src, event), 'CData', bg_image);
            set(obj.handles.song_stop, 'Callback', @(src, event) stopSong(obj, src, event), 'CData', bg_image);
            set(obj.handles.track_volslider, 'Callback', @(src, event) volumeTrack(obj, src, event));
            set(obj.handles.song_volslider, 'Callback', @(src, event) volumeSong(obj, src, event));
            set(obj.handles.track_axes, 'ButtonDownFcn', @(src, event) xPlayChange(obj, src, event));
            
            set(obj.handles.generateBut, 'Callback', @(src, event) generateOutputSelect(obj, src, event));
            
            % Windows Callbacks Definitions
            addlistener(obj.reverbWindow, 'finish', 'PostSet', @obj.refreshSong);
            addlistener(obj.karplusStrongWindow, 'finish', 'PostSet', @obj.karplusStrongRefresh);
            addlistener(obj.effectWindow, 'finish', 'PostSet', @obj.refreshSong);
            
            % Initial Values
            obj.playStatus = 0;
        end
                
        function obj = closeProgram(obj, ~, ~)
            % Save variables
%             try
%                 autosave.track_name = get(obj.handles.track_name, 'String');
%                 autosave.trackFileName = obj.trackFileName;
%                 autosave.trackFilePath = obj.trackFilePath;
%                 % Uncomment this for less size of autosave (but less speed)
% %                 for k = 1:length(obj.track)
% %                     obj.track{k}.x = [];
% %                     obj.track{k}.y = [];
% %                 end
%                 autosave.track = obj.track;
%                 autosave.trackInd = obj.trackInd;
%                 autosave.reverbData = obj.reverbWindow.saveData();
%                 save('autosave.mat', '-struct', 'autosave');
%             catch
%             end
            
            % Close Figure
            delete(obj.hfig);
            clear obj.hfig;
            delete(obj.reverbWindow);
            delete(obj.player);
        end
        
        function loadVariables(obj)
            % Load variables
            if exist('autosave.mat', 'file')
                try
                    autosave = load('autosave.mat');
                    set(obj.handles.track_name, 'String', autosave.track_name);
                    obj.trackFileName = autosave.trackFileName;
                    if ~isempty(obj.trackFileName)
                        set(obj.handles.track_inputname, 'String', obj.trackFileName);
                    end
                    obj.trackFilePath = autosave.trackFilePath;
                    obj.track = autosave.track;
                    % Uncomment this for less size of autosave (but less speed)
%                     for k = 1:length(obj.track) 
%                         obj.track{k}.refresh();
%                     end
                    obj.trackInd = autosave.trackInd;
                    obj.refreshTrackList();
                    if ~isempty(autosave.reverbData)
                        obj.reverbWindow.loadData(autosave.reverbData);
                    end
                catch
                end
            end
        end
        
        function obj = newTrackSelect(obj, ~, ~)
            trackNameStr = get(obj.handles.track_name, 'String');
            if ~isempty(obj.trackFileName) && ~isempty(trackNameStr) && exist(obj.trackFilePath, 'file')
                if strcmp(obj.trackFilePath(end-3:end), '.mid') % MIDI file
                    midi = readmidi(obj.trackFilePath);
                    for k = 1:length(midi.track)
                        midiAux.format = midi.format;
                        midiAux.ticks_per_quarter_note = midi.ticks_per_quarter_note;
                        midiAux.track = midi.track(k);
                        obj.karplusStrongWindow.defaultVariables();
                        obj.track{end+1} = Track([trackNameStr, num2str(k)], obj.trackFileName, midiAux, obj.karplusStrongWindow.Data);
                    end
                else % WAV file %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                    disp('WAV papa');
                end
                obj.trackInd = length(obj.track);
                obj.refreshTrackList();
                set(obj.handles.track_name, 'String', '');
            else
                if isempty(obj.trackFileName)
                    errordlg('There is no MID or WAV file selected', 'Inconsistent error');
                    return
                end
                if isempty(trackNameStr)
                    errordlg('Track name cannot be empty', 'Inconsistent error');
                    return
                end
                if ~exist(obj.trackFilePath, 'file')
                    errordlg('Error loading MID or WAV file. Please reload the file.. ', 'Inconsistent error');
                    return
                end
            end
        end
        
        function refreshTrackList(obj)
            listStr = '';
            for k = 1:length(obj.track)
                listStr = [listStr; {obj.track{k}.name}]; %#ok<AGROW>
            end
            set(obj.handles.trackList, 'String', listStr, 'Value', obj.trackInd);
            obj.refreshTrack();
        end
        
        function refreshTrack(obj)
            cla(obj.handles.track_axes);
            if ~isempty(obj.track)
                set(obj.handles.track_volnum, 'String', [num2str(100*obj.track{obj.trackInd}.volume), '%']);
                set(obj.handles.track_volslider, 'Value', obj.track{obj.trackInd}.volume);
                set(obj.handles.methodMenu, 'Value', obj.track{obj.trackInd}.methodInd);
                obj.refreshEffectsList();

                % Plot Track
                obj.xPlay = [0, 0; -1, 1];
                hold(obj.handles.track_axes, 'on');
                if ~isempty(obj.track{obj.trackInd}.y)
                    plot(obj.handles.track_axes, obj.track{obj.trackInd}.x, obj.track{obj.trackInd}.y, 'Color', [1,0,0]);
                end
                obj.playAxes = plot(obj.handles.track_axes, obj.xPlay(1,:), obj.xPlay(2,:), 'Color', [0,0,1]);
                set(obj.playAxes, 'XDataSource', 'obj.xPlay(1,:)');
                set(obj.playAxes, 'YDataSource', 'obj.xPlay(2,:)');
                if ~isempty(obj.track{obj.trackInd}.y)
                    xlim(obj.handles.track_axes, [obj.track{obj.trackInd}.x(1), obj.track{obj.trackInd}.x(end)]);
                    set(obj.handles.track_axes, 'Color', [0 0 0], 'XColor', [0.9 0.9 0.9], 'YColor', [0.9 0.9 0.9], 'YTick', []);
                    obj.player = audioplayer(obj.track{obj.trackInd}.y * obj.track{obj.trackInd}.volume, obj.track{obj.trackInd}.Fs);
                    set(obj.player, 'TimerFcn', @(src, event) refreshPlay(obj, src, event));
                    set(obj.player, 'StopFcn', @(src, event) finishPlay(obj, src, event));
                else
                    xlim(obj.handles.track_axes, [0, 1]);
                end
                
                switch obj.track{obj.trackInd}.methodInd
                    case 1 % Karplus Strong
                        obj.karplusStrongWindow.Data = obj.track{obj.trackInd}.methodSet;
                        obj.karplusStrongWindow.refreshWindow();
                    case 2 % No se
                        ;
                    case 3 % No se
                        ;
                        % Etc
                end
                        
            end
        end
        
        function obj = inputFileSelect(obj, ~, ~)
            [obj.trackFileName, trackPathName] = uigetfile({'*.mid'});% *.wav'});
            obj.trackFilePath = [trackPathName, obj.trackFileName];
            set(obj.handles.track_inputname, 'String', obj.trackFileName);
        end
        
        function obj = trackListSelect(obj, ~, ~)
            if ~isempty(obj.track)
                obj.trackInd = get(obj.handles.trackList, 'Value');
                obj.refreshTrack();
            end
        end
        
        function obj = trackRemove(obj, ~, ~)
            if ~isempty(obj.track)
                obj.track(:, obj.trackInd) = [];
                if obj.trackInd > length(obj.track)
                    obj.trackInd = obj.trackInd - 1;
                end
                obj.refreshTrackList();
            end
        end
        
        function obj = methodSelect(obj, ~, ~)
            if ~isempty(obj.track)
                obj.track{obj.trackInd}.methodInd = get(obj.handles.methodMenu, 'Value');
            end
        end
        
        function obj = methodSettings(obj, ~, ~)
            if ~isempty(obj.track)
                obj.karplusStrongWindow.visible();
            end
        end
        
        function obj = addEffectSelect(obj, ~, ~)
            if ~isempty(obj.track)
                effectsName = get(obj.handles.effectMenu, 'String');
                effectSelectInd = get(obj.handles.effectMenu, 'Value');
                obj.track{obj.trackInd}.effect{end+1} = Effect(effectsName(effectSelectInd));
                obj.track{obj.trackInd}.effectInd = length(obj.track{obj.trackInd}.effect);
                obj.refreshEffectsList();
            end
        end
        
        function refreshEffectsList(obj)
            listStr = '';
            for k = 1:length(obj.track{obj.trackInd}.effect)
                listStr = [listStr; obj.track{obj.trackInd}.effect{k}.name]; %#ok<AGROW>
            end
            set(obj.handles.effectsList, 'String', listStr, 'Value', obj.track{obj.trackInd}.effectInd);
        end
        
        function obj = effectListSelect(obj, ~, ~)
            if ~isempty(obj.track{obj.trackInd})
                obj.track{obj.trackInd}.effectInd = get(obj.handles.effectsList, 'Value');
                obj.refreshEffectsList();
            end
        end
        
        function obj = effectSettings(obj, ~, ~)
            obj.effectWindow.visible();
        end

        function obj = effectRemove(obj, ~, ~)
            if ~isempty(obj.track{obj.trackInd})
                obj.track{obj.trackInd}.effect(:, obj.track{obj.trackInd}.effectInd) = [];
                if obj.track{obj.trackInd}.effectInd > length(obj.track{obj.trackInd}.effect)
                    obj.track{obj.trackInd}.effectInd = obj.track{obj.trackInd}.effectInd - 1;
                end
                obj.refreshEffectsList();
            end
        end
        
        function obj = newSong(obj, ~, ~)
            disp('New Song');
            obj.refreshSong();
        end
        
        function obj = saveSong(obj, ~, ~)
            disp('Save Song');
        end
        
        function obj = loadSong(obj, ~, ~)
            disp('Load Song');
        end
        
        function obj = reverbSelect(obj, ~, ~)
            disp('Reverb Select');
        end
        
        function obj = reverbSettings(obj, ~, ~)
            obj.reverbWindow.visible();
        end
        
        function obj = playTrack(obj, ~, ~)
            if ~isempty(obj.track)
                if obj.playStatus % Pause
                    obj.playStatus = 0;
                    bg_image = imread('play.png');
                    set(obj.handles.track_play, 'CData', bg_image);
                    pause(obj.player);
                else % Play/Resume
                    obj.playStatus = 1;
                    bg_image = imread('pause.png');
                    set(obj.handles.track_play, 'CData', bg_image);
                    if isempty(obj.player) || (obj.player.currentSample() == 1) % Play
                        play(obj.player, floor(obj.xPlay(1)*obj.player.SampleRate+1));
                    else % Resume
                        resume(obj.player);
                    end
                end
            end
        end
        
        function obj = refreshPlay(obj, ~, ~)
            obj.xPlay(1,:) = (obj.player.CurrentSample/obj.player.SampleRate)*[1, 1];
            refreshdata(obj.playAxes, 'caller');
        end
        
        function obj = finishPlay(obj, ~, ~)
            obj.playStatus = 0;
            bg_image = imread('play.png');
            set(obj.handles.track_play, 'CData', bg_image);
            obj.refreshPlay();
        end
        
        function obj = stopTrack(obj, ~, ~)
            if ~isempty(obj.track)
                obj.playStatus = 0;
                bg_image = imread('play.png');
                set(obj.handles.track_play, 'CData', bg_image);
                stop(obj.player);
                obj.refreshPlay();
            end
        end
        
        function obj = xPlayChange(obj, src, ~)
            if (obj.playStatus == 0) && ~isempty(obj.track)
                if ~isempty(obj.player)
                    stop(obj.player);
                end
                pos = get(src,'CurrentPoint');
                obj.xPlay(1,:) = pos(1)*[1, 1];
                refreshdata(obj.playAxes, 'caller');
            end
        end
        
        function obj = volumeTrack(obj, src, ~)
            if ~isempty(obj.track)
                set(obj.handles.track_volnum, 'String', [num2str(100*get(src, 'Value')), '%']);
                obj.track{obj.trackInd}.volume = get(src, 'Value');
            end
        end
        
        function obj = playSong(obj, ~, ~)
            disp('Play Song');
        end
        
        function obj = stopSong(obj, ~, ~)
            disp('Stop Song');
        end
        
        function obj = volumeSong(obj, src, ~)
            set(obj.handles.song_volnum, 'String', [num2str(100*get(src, 'Value')), '%']);
        end
        
        function obj = generateOutputSelect(obj, ~, ~)
            obj.track{obj.trackInd}.methodSet = obj.karplusStrongWindow.Data;
            obj.track{obj.trackInd}.refresh();
            obj.refreshTrack();
        end
    end
    
    methods (Static)
        function refreshSong(~,~)
            disp('Exit');
        end
        
        function karplusStrongRefresh(~, ~)
%             obj.track{obj.trackInd}.methodSet = obj.karplusStrongWindow.Data;
            %obj.refreshTrack();
        end
    end
    
end